(** 

    Le module Tarotv est le module principal pour suivre une partie de
    tarot. Il  suppose que  vous avez trouvé  un moyen  de communiquer
    avec un interlocuteur qui respecte notre protocole.

    @author Vivien Kraus

*)

(** 

    Vous ne pouvez pas faire plusieurs requêtes en même temps. 

*)
exception Requete_en_cours

(** 

    Vous ne  pouvez pas  souhaiter être prévenu  en cas de  réponse si
    vous  n'avez pas  formulé de  requête. Cette  exception  est levée
    lorsque vous appelez la méthode {!val:Tarotv.traiter_reponse} sans
    avoir "fait" (i.e. assimilé) de requête auparavant.


*)
exception Pas_de_requete

(** 

    Lorsque  vous  voulez être  prévenu  si  votre  requête aboutit  /
    échoue, vous aurez à traiter un objet de ce type.

*)
type reponse =
  | Accepte (** Votre requête est acceptée. *)
  | Refuse (** Votre requête est explicitement refusée. *)
  | Erreur (** Votre requête n'a pas pu être traitée. *)

(** 

    Le  type principal. Contient  toutes les  informations nécessaires
    pour suivre la partie.

*)
type t

(**

   Crée un  nouveau client,  qui n'a pas  assimilé le fait  qu'il soit
   connecté.

   Veuillez     appeler    {!assimiler_information_connexion}    avant
   d'assimiler vos requêtes / réponses.

*)
val make: unit -> t

(**

   Lorsque vous voulez  envoyer une requête au serveur,  vous devez en
   informer le module pour qu'il puisse comprendre ce qui se passe. Je
   ne garantis  pas le  fait que  vous soyez informé  si la  partie se
   désynchronise du  serveur suite à cet oubli,  ni l'éventuelle façon
   qu'aurait cette  désynchronisation de  se signaler. Si  vous n'avez
   pas  compris cette  phrase (j'avoue,  j'ai fait  exprès), ASSIMILEZ
   TOUTES VOS REQUÊTES AVANT DE LES ENVOYER.

   C'est également valable  du côté serveur. Testez si  le joueur peut
   légitimement faire  cette requête, puis  assimilez-la, puis envoyez
   la réponse (assimilée, bien sûr).

   Côté client, toujours, vous  disposez d'outils pour savoir si votre
   requête va être rejetée ou acceptée. Ainsi, {!Tarotv.peut_appeler},
   {!Tarotv.peut_ecarter} et  {!Tarotv.peut_jouer} peuvent vous éviter
   de surcharger le serveur. Donc  la procédure est : vérifier si j'ai
   le droit de faire  ça, assimiler la requête, éventuellement appeler
   {!traiter_reponse}, envoyer le message correspondant.

*)

(** 

    Assimile que vous voulez appeler une carte. 

    [assimiler_requete_appel t carte] comprendra que la prochaine fois
    qu'on  vous  refusera  quelque   chose,  ce  sera  l'appel.  Jetez
    également un œil à {!Tarotv.peut_appeler}.

*)
val assimiler_requete_appel: t -> Carte.carte -> unit

(**

   Assimile que  vous voulez  JOUER une carte.  Pour vous  aider, vous
   pouvez regarder {!Tarotv.peut_jouer}.

*)
val assimiler_requete_carte: t -> Carte.carte -> unit

(**

   Assimile  que vous  voulez vous  déconnecter. Ça  n'a  pas beaucoup
   d'intérêt,  sauf  si vous  arrivez  à  faire  une requête  x,  vous
   déconnecter, vous reconnecter, envoyer une demande d'identification
   avant que  la requête x n'arrive  au serveur. Dans  ce cas, l'oubli
   d'assimilation      se       traduira      en      une      superbe
   {!Tarotv.Requete_en_cours}.

*)
val assimiler_requete_deconnecter: t -> unit

(** 
    
    Assimile que  vous voulez  faire un écart.  Pour vous  aider, vous
    pouvez voir  {!Tarotv.peut_ecarter} si  vous avez déjà  vos cartes
    sélectionnées,  et   {!Tarotv.carte_ecartable_marginale}  si  vous
    souhaitez compléter votre écart.

*)
val assimiler_requete_ecart: t -> Carte.carte list -> unit

(**

   Assimile que vous voulez vous identifier.

*)
val assimiler_requete_identifier: t -> string -> unit

(**

   Assimile que vous souhaitez inviter  une liste de gens à jouer avec
   vous. Sauf  cas où le joueur  invité arrive / parte  avant que vous
   n'en soyez  informé, vous devrier  piocher dans la  liste retournée
   par {!joueurs_disponibles}.

*)
val assimiler_requete_inviter: t -> string list -> unit

(**

   Assimile que vous souhaitez prendre ou passer. Vous pouvez vérifier
   les enchères de chacun,  via {!enchere_de}, ou simplement l'enchère
   maximale, via {!enchere_maximale}.

*)
val assimiler_requete_prise: t -> int -> unit

(**

   Si vous souhaitez  être informé du résultat de  votre requête, vous
   devez le signaler après avoir assimilé votre requête.
   
   [traiter_reponse  t resultat]  enregistre qu'il  faudra  appeler la
   fonction [resultat] lorsque la réponse sera connue.

   @raise  Pas_de_requete L'exception  est  levée si  vous n'avez  pas
   assimilé votre requête.

*)  
val  traiter_reponse:  t  ->  (reponse ->  unit)  ->  unit  

(**
   
   Si  vous ne  savez pas  si  une autre  chaîne de  commandes a  fait
   assimiler  une requête,  vous  pouvez toujours  le  savoir grâce  à
   requete_en_cours.
   
*)
val requete_en_cours: t -> bool

(*val informer: t -> (string ->  unit) -> unit -> unit*) (* informer t
afficher renvoie une fonction qui arrête le phénomène suivant : chaque
fois qu'on appelle une fonction  sur Tarotv avec ce joueur, on appelle
la fonction.*)

(**

   Lorsque  le serveur  vous  envoie un  message,  vous devez  appeler
   immédiatement  l'une des  fonctions  suivantes, afin  de garder  la
   partie synchronisée avec celui-ci.

   Côté serveur,  vous devez également  assimiler ce que  vous envoyez
   aux cliens, ne  serait-ce que pour retrouver le  chien, qui est par
   définition les 3 cartes qu'aucun des participants ne possède.

   Notons que les ensembles sont  la plupart du temps des listes, sauf
   pour     les     fonctions     {!assimiler_information_noms}     et
   {!assimiler_information_resultat},  où  le  fait  que  les  joueurs
   soient au  nombre de  5 est souligné  en utilisant un  tableau. Les
   enchères se représentent par un numéro : 0 correspond à passer, 1 à
   une petite,  2 à une  garde, 3 à  une garde sans  et 4 à  une garde
   contre. Les  joueurs sont toujours représentés par  leur numéro, un
   nombre entre 0 et 4 inclus (5 valeurs), contrairement à ce que l'on
   utilise pour deviner le jeu des adversaires.

   Enfin,  la  fonction  [assimiler_information_connexion]  doit  être
   appelée   dès   que   vous    êtes   connecté,   et   la   fonction
   [assimiler_information_deconnexion] dès que vous êtes déconnecté.

*)

val assimiler_information_appel: t -> unit
val assimiler_information_atout_au_chien: t -> Carte.carte list -> 
  unit
val assimiler_information_carte: t -> Carte.carte -> unit
val assimiler_information_chien: t -> Carte.carte list -> unit
val assimiler_information_connexion: t -> unit
val assimiler_information_contrat: t -> int -> unit
val assimiler_information_contrat_final: t -> int -> int -> 
  Carte.carte -> unit
val assimiler_information_deconnexion: t -> unit
val assimiler_information_distribution: t -> Carte.carte list -> unit
val assimiler_information_entree: t -> string -> unit
val assimiler_information_erreur: t -> unit
val assimiler_information_jeu: t -> unit
val assimiler_information_noms: t -> string array -> unit
val assimiler_information_numero: t -> int -> unit
val assimiler_information_pli: t -> int -> unit
val assimiler_information_resultat: t -> int array -> unit
val assimiler_information_sortie: t -> string -> unit

(** 

    Voici maintenant  l'ensemble des méthodes que  vous pouvez appeler
    pour vous aider à prendre une décision.

*)

(** Retourne None jusqu'au message "contrat final". *)
val carte_appelee: t -> Carte.carte option

(** 

    Sachant les cartes que j'ai déjà sélectionnées, je peux écarter la
    suivante parmi :

    [carte_ecartable_marginale t selectionnees]

    Attention :  si le  résultat est une  liste vide, ça  peut vouloir
    dire que j'ai déjà sélectionné  au moins trois cartes, ou que j'ai
    sélectionné une carte interdite.

*)
val carte_ecartable_marginale: t -> Carte.carte list -> 
  Carte.carte list
val cartes_appelables: t -> Carte.carte list
val cartes_jouables: t -> Carte.carte list
val chien: t -> Carte.carte list
val couleur_demandee: t -> Carte.couleur option

(** N'a pas de sens pour la phase d'enchères. *)
val devant_jouer: t -> int

(** 
    
    Retourne soit  rien (l'écart  n'est pas fait),  soit une  liste de
    cartes  dont vous  êtes sûr  qu'elle  ne comprend  que des  cartes
    écartées par le preneur.

*)
val ecart_connu: t -> Carte.carte list option
val enchere_de: t -> int -> int option

(** 

    L'enchère maximale  est le couple (preneur,  enchère). Par défaut,
    le  preneur est  0 et  l'enchère  est -1.  On peut  le déduire  de
    {!enchere_de}.

*)
val enchere_maximale: t -> int * int (* preneur * enchère *)

(** Retourne vrai ssi vous avez trouvé 4 adversaires. *)
val en_jeu: t -> bool

(** 

    Retourne vrai  entre le moment où vous  assimilez l'information de
    connexion et de déconnexion.

 *)
val est_connecte: t -> bool

(**

   Le  joueur appelé  est déduit  dès que  l'on voit  passer  la carte
   appelée.

*)
val joueur_appele: t -> int option
val joueur_maitre: t -> int option

(** Il s'agit du premier joueur qui a joué à ce tour. *)
val joueur_ouverture: t -> int

(** retourne [] lorsque ous êtes {!en_jeu}. *)
val joueurs_disponibles: t -> string list
val mes_adversaires: t -> string array
val mon_jeu: t -> Carte.carte list
val mon_nom: t -> string
val mon_numero: t -> int option
val nombre_plis_joues: t -> int

(**

   [petit_au_bout t]  retourne [None]  si on n'en  est pas  au dernier
   tour ou si le petit n'est pas présent, [Some `Attaque] si l'attaque
   mène le petit au bout, [Some  `Defense] si la défense mène le petit
   au bout.

*)
val petit_au_bout: t -> [`Attaque | `Defense] option
val peut_appeler: t -> Carte.carte -> bool
val peut_ecarter: t -> Carte.carte list -> bool
val peut_jouer: t -> Carte.carte -> bool

(** 

    Le pli  est terminé entre le moment  où vous recevez le  jeu de la
    cinquième carte  et le  moment où le  serveur annonce  le prochain
    joueur.

*)
val pli_termine: t -> bool

(**

   Le plus gros atout est la valeur de l'atout maître sur le tapis.

*)
val plus_gros_atout: t -> Carte.valeur option

(** 

    À chaque  fois que l'on  assimile un  message "pli", le  nombre de
    points   et  de   bouts  gagnés   par   chacun  se   met  à   jour
    automatiquement. [points]  est capable de déterminer  le nombre de
    demi-points gagnés et  le nombre de bouts gagnés  à n'importe quel
    moment du jeu. Si on connaît son équipe, on compte tous les points
    / bouts de  l'équipe. Si on connaît le chien  et qu'il me revient,
    on ajoute les points qui sont dedans. Les équipes sont déterminées
    par le deuxième argument, qui peut être soit [Tarotv.joueur_appele
    client], soit le joueur issu d'une simulation de l'IA.

    Plus  précisément,  la  fonction  renvoie  le  couple  (nombre  de
    demi-points, nombre de bouts).

*)
val points: t -> int option -> (int * int)
val preneur: t -> int option

(**

   Retourne la liste des cartes jouées à un certain pli.
   
   Lorsque la fonction est appelée sur un pli futur, elle retourne une
   liste vide.

   Lorsque la fonction est appelée  sur un pli passé, elle retourne la
   liste    des     triplets    (joueur-qui-a-pose-la-carte,    carte,
   joueur-qui-remporte-la-carte). Attention, il  peut y avoir 6 cartes
   :  lorsque quelqu'un  joue l'excuse,  il la  remporte et  le joueur
   maître remporte la fausse excuse, sauf au dernier pli.

   Lorsque la fonction  est appelée sur le pli  présent, elle retourne
   la   liste   des   triplets   (joueur-qui-a-posé-la-carte,   carte,
   joueur-qui-remporte-la-carte-si-le-pli-se-termine-maintenant).
   
*)
val repartition_cartes: t -> int -> (int * Carte.carte * int) list

(** 

    Les fonctions  suivantes peuvent  vous dire si  on vous  attend ou
    pas. Conseil  : utilisez-les après  avoir assimilé un  message. Le
    comportement lorsqu'une requête est en cours est que vous ne devez
    ni  appeler,  ni  écarter,  ni  jouer, ni  priser.  Ceci  explique
    pourquoi il faut  vérifier la validité de la  requête côté serveur
    juste avant d'assimiler la requête.

*)

val doit_appeler: t -> bool
val doit_ecarter: t -> bool
val doit_jouer: t -> bool
val doit_priser: t -> bool

(**

   Le  client analyse  le  jeu à  chaque fois  que  vous assimilez  un
   message  pour tenter  de déterminer  le jeu  des adversaires.  Plus
   précisément,  il  interdit  toutes   les  cartes  des  couleurs  où
   l'adversaire  coupe,  et tous  les  atouts  supérieurs à  ceux  que
   l'adversaire  a  sous-coupé.    Lorsque  l'adversaire  "pisse",  on
   interdit à  la fois les  cartes à la  couleur demandée et  tous les
   atouts.  Bien sûr,  on ne  déduit rien  du jeu  de l'excuse,  sinon
   qu'elle n'est plus disponible.

   La valeur de retour est la matrice de booléens [interdit] telle que
   [interdit.(i).(j)] vaut vrai ssi le joueur numéro i ne {b peut pas}
   jouer la  carte [Carte.carte_of_int j], et  [interdit.(5).(j)] vaut
   vrai ssi la carte [Carte.carte_of_int j]  ne {b peut pas} être dans
   le chien ou l'écart.
   
   On retourne toujours une copie.
*)
val interdictions: t -> bool array array

(**

   L'état du client est la donnée de ce nombre impressionnant de valeurs.

*)
type etat =
  {
    connecte: bool;
    mon_nom: string;
    en_jeu: bool;
    joueurs_disponibles: string list;

    mes_adversaires: string array;
    mon_numero: int option;
    nombre_plis_joues: int;
    dp_gagnes: int;
    bouts_remportes: int;
    petit_au_bout: [`Attaque | `Defense] option;
    mon_jeu: Carte.carte list;

    preneur: int option;
    carte_appelee: Carte.carte option;
    joueur_appele: int option;
    chien: Carte.carte list;
    ecart_minimum: Carte.carte list;
    ecart_maximum: Carte.carte list;
    main_maximale: Carte.carte list array;
    encheres: int option array;

    couleur_demandee: Carte.couleur option;
    joueur_ouverture: int;
    devant_jouer: int;
    joueur_maitre: int option;
    plus_gros_atout: Carte.valeur option;
    pli_termine: bool;
    cartes_par_pli: (int * Carte.carte * int) list array;

    appelables: Carte.carte list;
    jouables: Carte.carte list;

    doit_appeler: bool;
    doit_ecarter: bool;
    doit_jouer: bool;
    doit_priser: bool;

    requete_en_cours: bool;
  }

val etat_de: t -> etat
